/**
 * Copyright (c) 2018-2025, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: trace system
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2025-07-19     JasonHu           Init
 */

#include <base/trace.h>
#define NX_LOG_NAME "trace"
#define NX_LOG_LEVEL NX_LOG_DBG
#include <base/log.h>
#include <base/malloc.h>
#include <base/initcall.h>
#include <base/string.h>
#include <base/smp.h>
#include <base/uaccess.h>
#include <base/driver.h>

#define EVENT_DRV_NAME "Trace Event Driver"
#define SOURCE_DRV_NAME "Trace Source Driver"

/**
 * trace device extension
 */
typedef struct TraceDeviceExtension {
    NX_TraceEvent *event;
    NX_EventRecord records[NX_EVENT_RECORD_MAX_NR];
} TraceDeviceExtension;

/**
 * trace cmd
 */
#define NX_TRACE_SET_FILTER 1
#define NX_TRACE_CLEAR_FILTER 2
#define NX_TRACE_RESET_FILTER 3

typedef struct NX_TraceFilterBuf {
    NX_TraceFilter filter;
    NX_U32 value;
} NX_TraceFilterBuf;

#define GetTraceEventFifoIndex(event, idx) ((event)->fifo[idx])
#define GetTraceEventFifo(event) GetTraceEventFifoIndex(event, NX_SMP_GetIdx())

NX_PRIVATE NX_LIST_HEAD(TraceEventList);

NX_PRIVATE NX_SPIN_DEFINE_UNLOCKED(TraceEventLock);

NX_PRIVATE NX_Driver *TraceEventDriver;
NX_PRIVATE NX_Driver *EventSourceDriver;

NX_PRIVATE NX_Bool NX_TraceFilterCheck(NX_EventHeader *header, NX_U32 filter, NX_U32 filterVal[]);

NX_EventSource *NX_EventSourceCreate(char *name)
{
    NX_EventSource *source;

    source = NX_MemAllocEx(NX_EventSource);
    if (source == NX_NULL)
    {
        return NX_NULL;
    }

    NX_ListInit(&source->list);
    NX_MemZero(source->name, sizeof(source->name));
    NX_StrCopy(source->name, name);
    source->magic = NX_TRACE_SOURCE_MAGIC;
    return source;
}

NX_Error NX_EventSourceDestroy(NX_EventSource *source)
{
    if (source == NX_NULL)
    {
        return NX_EINVAL;
    }
    if (source->magic != NX_TRACE_SOURCE_MAGIC)
    {
        return NX_EPERM;
    }
    source->magic = 0;
    NX_MemFree(source);

    return NX_EOK;
}

NX_Error NX_EventSourceRegister(NX_EventSource *source, char *traceName)
{
    NX_TraceEvent *event;
    NX_UArch level;
    NX_Device *device;
    char devname[NX_DEVICE_NAME_LEN];
    
    event = NX_TraceEventSearch(traceName); 
    if (event == NX_NULL)
    {
        return NX_EINVAL;
    }

    NX_MemZero(devname, NX_DEVICE_NAME_LEN);
    NX_SNPrintf(devname, NX_DEVICE_NAME_LEN, "e.%s.%s", traceName, source->name);

    if (NX_DriverAttachDevice(EventSourceDriver, devname, &device) != NX_EOK)
    {
        NX_LOG_E("attach device %s failed!", devname);
        return NX_ENOMEM;
    }

    source->event = event;

    NX_SpinLockIRQ(&TraceEventLock, &level);
    NX_ListAdd(&source->list, &event->sourceList);
    NX_SpinUnlockIRQ(&TraceEventLock, level);

    device->extension = event;

    source->device = device;

    return NX_EOK;
}

NX_Error NX_EventSourceUnregister(NX_EventSource *source)
{
    NX_UArch level;

    source->event = NX_NULL;
    source->enable = NX_False;

    NX_DriverDetachDevice(EventSourceDriver, source->device->name);

    NX_SpinLockIRQ(&TraceEventLock, &level);
    NX_ListDel(&source->list);
    NX_SpinUnlockIRQ(&TraceEventLock, level);
    return NX_EOK;
}

NX_Error NX_EventSourceEanble(NX_EventSource *source, NX_Bool enable)
{
    source->enable = enable;
    return NX_EOK;
}

NX_PRIVATE void DestroyTraceFifo(NX_TraceEvent *event)
{
    NX_I32 idx;
    
    for (idx = 0; idx < NX_MULTI_CORES_NR; idx++)
    {
        if (event->fifo[idx])
        {
            NX_FifoDestroy(event->fifo[idx]);
        }
    }
}

NX_PRIVATE NX_Error CreateTraceFifo(NX_TraceEvent *event, NX_Size eventCount)
{
    NX_I32 idx;
    
    for (idx = 0; idx < NX_MULTI_CORES_NR; idx++)
    {
        event->fifo[idx] = NX_FifoCreate(eventCount * sizeof(NX_EventRecord));
        if (!event->fifo[idx])
        {
            DestroyTraceFifo(event);
            return NX_ENOMEM;
        }
    }

    return NX_EOK;
}

NX_TraceEvent *NX_TraceEventCreate(char *name, NX_Size eventCount)
{
    NX_TraceEvent *event;
    NX_UArch level;
    NX_Device *device;
    char devname[NX_DEVICE_NAME_LEN];

    event = NX_MemAllocEx(NX_TraceEvent);
    if (!event || !eventCount)
    {
        return NX_NULL;
    }

    if (CreateTraceFifo(event, eventCount) != NX_EOK)
    {
        NX_MemFree(event);
        return NX_NULL;
    }

    NX_MemZero(devname, NX_DEVICE_NAME_LEN);
    NX_SNPrintf(devname, NX_DEVICE_NAME_LEN, "e.%s", name);

    if (NX_DriverAttachDevice(TraceEventDriver, devname, &device) != NX_EOK)
    {
        NX_LOG_E("attach device %s failed!", devname);
        DestroyTraceFifo(event);
        NX_MemFree(event);
        return NX_NULL;
    }

    event->magic = NX_TRACE_EVENNT_MAGIC;
    event->enable = NX_False;
    NX_ListInit(&event->list);
    NX_ListInit(&event->sourceList);
    NX_SpinInit(&event->sourceLock);
    NX_SemaphoreInit(&event->eventsSem, 0);

    NX_MemZero(event->name, sizeof(event->name));
    NX_StrCopy(event->name, name);

    NX_SpinLockIRQ(&TraceEventLock, &level);
    NX_ListAdd(&event->list, &TraceEventList);
    NX_SpinUnlockIRQ(&TraceEventLock, level);

    device->extension = event;

    event->device = device;

    return event;
}

NX_TraceEvent *NX_TraceEventSearch(char *name)
{
    NX_TraceEvent *node;
    NX_UArch level;
    
    NX_SpinLockIRQ(&TraceEventLock, &level);

    NX_ListForEachEntry (node, &TraceEventList, list)
    {
        if (!NX_StrCmp(node->name, name))
        {
            NX_SpinUnlockIRQ(&TraceEventLock, level);
            return node;
        }
    }
    NX_SpinUnlockIRQ(&TraceEventLock, level);
    return NX_NULL;
}

NX_Error NX_TraceEventEnable(NX_TraceEvent *event)
{
    event->enable = NX_True;
    return NX_EOK;
}

NX_Error NX_TraceEventDisable(NX_TraceEvent *event)
{
    event->enable = NX_False;
    return NX_EOK;
}

NX_PRIVATE void UnregisterAllEventSource(NX_TraceEvent *event)
{
    NX_EventSource *source, *next;
    
    NX_ListForEachEntrySafe(source, next, &event->sourceList, list)
    {
        NX_EventSourceUnregister(source);
    }
}

NX_Error NX_TraceEventDestroy(NX_TraceEvent *event)
{
    NX_UArch level;

    if (event->magic != NX_TRACE_EVENNT_MAGIC)
    {
        return NX_EPERM;
    }

    event->magic = 0;

    /* unregister all source */
    UnregisterAllEventSource(event);

    DestroyTraceFifo(event);

    NX_DriverDetachDevice(TraceEventDriver, event->device->name);

    NX_SpinLockIRQ(&TraceEventLock, &level);
    NX_ListDel(&event->list);
    NX_SpinUnlockIRQ(&TraceEventLock, level);

    return NX_EOK;
}

NX_Error NX_TraceEventSetFilter(NX_TraceEvent *event, NX_TraceFilter filter, NX_U32 value)
{
    if (event->magic != NX_TRACE_EVENNT_MAGIC)
    {
        return NX_EPERM;
    }

    if (!(filter >= NX_TRACE_FILTER_FIRST && filter < NX_TRACE_FILTER_NR))
    {
        return NX_EINVAL;
    }

    event->filterVal[filter] = value;

    return NX_EOK;
}

NX_Error NX_TraceEventClearFilter(NX_TraceEvent *event, NX_TraceFilter filter)
{
    if (event->magic != NX_TRACE_EVENNT_MAGIC)
    {
        return NX_EPERM;
    }

    if (!(filter >= NX_TRACE_FILTER_FIRST && filter < NX_TRACE_FILTER_NR))
    {
        return NX_EINVAL;
    }

    event->filterVal[filter] = 0;

    return NX_EOK;
}

NX_Error NX_TraceEventResetFilter(NX_TraceEvent *event)
{
    if (event->magic != NX_TRACE_EVENNT_MAGIC)
    {
        return NX_EPERM;
    }

    NX_MemZero(event->filterVal, sizeof(event->filterVal));

    return NX_EOK;
}

NX_Error NX_EventSourceSetFilter(NX_EventSource *source, NX_TraceFilter filter, NX_U32 value)
{
    if (source->magic != NX_TRACE_SOURCE_MAGIC)
    {
        return NX_EPERM;
    }

    if (!(filter >= NX_TRACE_FILTER_FIRST && filter < NX_TRACE_FILTER_NR))
    {
        return NX_EINVAL;
    }

    source->filterVal[filter] = value;

    return NX_EOK;
}

NX_Error NX_EventSourceClearFilter(NX_EventSource *source, NX_TraceFilter filter)
{
    if (source->magic != NX_TRACE_SOURCE_MAGIC)
    {
        return NX_EPERM;
    }

    if (!(filter >= NX_TRACE_FILTER_FIRST && filter < NX_TRACE_FILTER_NR))
    {
        return NX_EINVAL;
    }

    source->filterVal[filter] = 0;

    return NX_EOK;
}

NX_Error NX_EventSourceResetFilter(NX_EventSource *source)
{
    if (source->magic != NX_TRACE_EVENNT_MAGIC)
    {
        return NX_EPERM;
    }

    NX_MemZero(source->filterVal, sizeof(source->filterVal));

    return NX_EOK;
}

NX_Error NX_TraceEventWrite(NX_EventSource *source, NX_EventHeader *header, NX_U8 *data, NX_Size len)
{
    NX_TraceEvent *event;
    NX_EventRecord record;
    NX_Fifo *fifo;
    NX_Bool countEvents = NX_True;

    event = source->event;

    if (!event)
    {
        return NX_EPERM;
    }

    if (!event->enable || !source->enable)
    {
        return NX_EPERM;
    }

    if (len > NX_EVENT_RECORD_DATA_SIZE)
    {
        return NX_EINVAL;
    }

    /* check event source filter */
    if (!NX_TraceFilterCheck(header, event->filter, event->filterVal))
    {
        return NX_EAGAIN;
    }

    /* check event source filter */
    if (!NX_TraceFilterCheck(header, source->filter, source->filterVal))
    {
        return NX_EAGAIN;
    }

    fifo = GetTraceEventFifo(event);

    if (!NX_FifoAvaliable(fifo))
    {
        countEvents = NX_False;
        NX_FifoRead(fifo, (const NX_U8 *)&record, sizeof(NX_EventRecord));
        if (NX_FifoAvaliable(fifo) < sizeof(NX_EventRecord))
        {
            return NX_ENORES;
        }
    }

    record.header = *header;
    record.header.time = NX_ClockGetMillisecond();

    NX_MemCopy(record.data, data, len);

    NX_Size wr = NX_FifoWrite(fifo, (const NX_U8 *)&record, sizeof(NX_EventRecord));
    if (wr != 0 && countEvents == NX_True)
    {
        NX_SemaphoreSignal(&event->eventsSem);
    }

    return NX_EOK;
}

NX_Bool NX_TraceFilterCheck(NX_EventHeader *header, NX_U32 filter, NX_U32 filterVal[])
{
    NX_Offset off;
    NX_Bool passed;

    passed = NX_True;

    if (filter)
    {
        for (off = NX_TRACE_FILTER_FIRST; off < NX_TRACE_FILTER_NR; off++)
        {
            switch ((filter & (1U << off)))
            {
            case NX_TRACE_FILTER_MASK_VER:
                if (header->version != filterVal[NX_TRACE_FILTER_VER])
                {
                    passed = NX_False;
                }
                break;
            case NX_TRACE_FILTER_MASK_LEVEL:
                if (header->level != filterVal[NX_TRACE_FILTER_LEVEL])
                {
                    passed = NX_False;
                }
                break;
            case NX_TRACE_FILTER_MASK_TASK:
                if (header->task != filterVal[NX_TRACE_FILTER_TASK])
                {
                    passed = NX_False;
                }
                break;
            case NX_TRACE_FILTER_MASK_KEY:
                if (header->keyword != filterVal[NX_TRACE_FILTER_KEY])
                {
                    passed = NX_False;
                }
                break;
            default:
                break;
            }
        }
    }
    return passed;
}

NX_PRIVATE NX_Fifo *GetFirstEventFifo(NX_TraceEvent *event)
{
    NX_I32 idx;
    NX_U64 firstEventTime;
    NX_Fifo *eventFifo;
    NX_Fifo *fifo;
    NX_EventRecord record;

    eventFifo = NX_NULL;
    firstEventTime = 0;
    for (idx = 0; idx < NX_MULTI_CORES_NR; idx++)
    {
        fifo = GetTraceEventFifoIndex(event, idx);
        if (NX_FifoLen(fifo) > 0)
        {
            if (NX_FifoPeek(fifo, (const NX_U8 *)&record, sizeof(record)) != 0)
            {
                if (record.header.time > firstEventTime)
                {
                    firstEventTime = record.header.time;
                    eventFifo = fifo;
                }
            }
        }
    }
    return eventFifo;
}

#define TRACE_TIME_OUT (1000 * 60 * 3)

NX_PRIVATE NX_Error NX_TraceEventHandle(NX_TraceEvent *event, NX_EventRecord *record, NX_TimeVal timeout)
{
    NX_Error err = NX_EFAULT;
    NX_EventHeader *header;
    NX_Fifo *fifo;

    /*  wait 30 s */
    err = NX_SemaphoreWaitTimeout(&event->eventsSem, timeout);
    if (err != NX_EOK)
    {
        return err;
    }

    fifo = GetFirstEventFifo(event);
    if (fifo == NX_NULL)
    {
        return NX_ENORES;
    }

    if (NX_FifoLen(fifo) > 0)
    {
        if (NX_FifoRead(fifo, (const NX_U8 *)record, sizeof(*record)) != 0)
        {
            /* check filter */
            header = &record->header;
            if (NX_TraceFilterCheck(header, event->filter, event->filterVal) == NX_True)
            {
                err = NX_EOK;
            }
        }
    }
    else
    {
        err = NX_ENORES;
    }
    return err;
}

NX_PRIVATE NX_Error TraceGetEventRecords(NX_TraceEvent *event, NX_EventRecord *records,
    NX_Size maxCount, NX_Size *outCount, NX_TimeVal initTimeout)
{
    NX_EventRecord *record;
    NX_Error err;
    NX_Size count;
    NX_SSize tmpCount;
    
    tmpCount = (NX_SSize)maxCount;
    
    if (!event || !tmpCount || records == NX_NULL || outCount == NX_NULL)
    {
        return NX_EINVAL;
    }

    if (tmpCount > NX_EVENT_RECORD_MAX_NR)
    {
        tmpCount = NX_EVENT_RECORD_MAX_NR;
    }

    *outCount = 0;
    count = 0;

    record = records;

    err = NX_TraceEventHandle(event, record, initTimeout);
    if (err == NX_ETIMEOUT)
    {
        return err;
    }

    /* read left events */
    record++;
    count++;
    tmpCount--;
    
    while (tmpCount-- > 0)
    {
        err = NX_TraceEventHandle(event, record, 0);
        if (err != NX_EOK)
        {
            break;
        }
        record++;
        count++;
    }
    
    *outCount = count;

    return NX_EOK;
}

NX_PRIVATE NX_Error EventDevOpen(struct NX_Device *device, NX_U32 flags)
{
    TraceDeviceExtension *ext;

    ext = NX_MemAlloc(sizeof(TraceDeviceExtension));
    if (!ext)
    {
        return NX_ENOMEM;
    }
    ext->event = device->extension;

    device->extension = ext;
    return NX_EOK;
}

NX_PRIVATE NX_Error EventDevClose(struct NX_Device *device)
{
    if (!device->extension)
    {
        return NX_EFAULT;
    }

    TraceDeviceExtension *ext = device->extension;
    device->extension = ext->event;

    NX_MemFree(ext);
    return NX_EOK;
}

NX_PRIVATE NX_Error EventDevRead(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    NX_Error err;
    NX_TimeVal waitTime = TRACE_TIME_OUT;
    TraceDeviceExtension *ext = device->extension;
    NX_TraceEvent *event = (NX_TraceEvent *)ext->event;
    NX_Size nrecords = 0;

    err = TraceGetEventRecords(event, ext->records, len / sizeof(NX_EventRecord), &nrecords, waitTime);

    if (err == NX_EOK)
    {
        NX_CopyToUser((char *)buf, (char *)ext->records, nrecords * sizeof(NX_EventRecord));
    }
    else
    {
        nrecords = 0;
    }

    if (outLen)
    {
        *outLen = nrecords * sizeof(NX_EventRecord);
    }

    return err;
}

NX_PRIVATE NX_Error EventDevWrite(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    if (outLen)
    {
        *outLen = len;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error EventDevControl(struct NX_Device *device, NX_U32 cmd, void *arg)
{
    NX_Error err = NX_EINVAL;
    TraceDeviceExtension *ext = device->extension;
    NX_TraceEvent *event = (NX_TraceEvent *)ext->event;
    NX_TraceFilterBuf *buf = (NX_TraceFilterBuf *)arg;
    NX_TraceFilterBuf kbuf;

    switch (cmd)
    {
    case NX_TRACE_SET_FILTER:
        if (!arg) {
            err = NX_EINVAL;
            break;
        }
        NX_CopyFromUser((char *)&kbuf, (char *)buf, sizeof(NX_TraceFilterBuf));
        err = NX_TraceEventSetFilter(event, kbuf.filter, kbuf.value);
        break;
    case NX_TRACE_CLEAR_FILTER:
        if (!arg) {
            err = NX_EINVAL;
            break;
        }
        NX_CopyFromUser((char *)&kbuf, (char *)buf, sizeof(NX_TraceFilterBuf));
        err = NX_TraceEventClearFilter(event, kbuf.filter);
        break;
    case NX_TRACE_RESET_FILTER:
        err = NX_TraceEventResetFilter(event);
        break;
    default:
        break;
    }

    return err;
}

NX_PRIVATE NX_DriverOps TraceEventDriverOps = {
    .open = EventDevOpen,
    .close = EventDevClose,
    .read = EventDevRead,
    .write = EventDevWrite,
    .control = EventDevControl,
};

NX_PRIVATE void TraceEventDriverInit(void)
{
    NX_Driver *driver = NX_DriverCreate(EVENT_DRV_NAME, NX_DEVICE_TYPE_VIRT, 0, &TraceEventDriverOps);
    if (driver == NX_NULL)
    {
        NX_LOG_E("create driver failed!");
        return;
    }

    if (NX_DriverRegister(driver) != NX_EOK)
    {
        NX_LOG_E("register driver %s failed!", EVENT_DRV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
    
    TraceEventDriver = driver;

    NX_LOG_I("init %s driver success!", EVENT_DRV_NAME);
}

NX_PRIVATE void TraceEventDriverExit(void)
{
    NX_DriverCleanup(EVENT_DRV_NAME);
}

NX_PRIVATE NX_Error SourceDevOpen(struct NX_Device *device, NX_U32 flags)
{
    return NX_EOK;
}

NX_PRIVATE NX_Error SourceDevClose(struct NX_Device *device)
{
    return NX_EOK;
}

NX_PRIVATE NX_Error SourceDevRead(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    return NX_EPERM;
}

NX_PRIVATE NX_Error SourceDevWrite(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    if (outLen)
    {
        *outLen = len;
    }
    return NX_EPERM;
}

NX_PRIVATE NX_Error SourceDevControl(struct NX_Device *device, NX_U32 cmd, void *arg)
{
    NX_Error err = NX_EINVAL;
    NX_EventSource *source = (NX_EventSource *)device->extension;
    NX_TraceFilterBuf *buf = (NX_TraceFilterBuf *)arg;
    NX_TraceFilterBuf kbuf;

    switch (cmd)
    {
    case NX_TRACE_SET_FILTER:
        if (!arg) {
            err = NX_EINVAL;
            break;
        }
        NX_CopyFromUser((char *)&kbuf, (char *)buf, sizeof(NX_TraceFilterBuf));
        err = NX_EventSourceSetFilter(source, kbuf.filter, kbuf.value);
        break;
    case NX_TRACE_CLEAR_FILTER:
        if (!arg) {
            err = NX_EINVAL;
            break;
        }
        NX_CopyFromUser((char *)&kbuf, (char *)buf, sizeof(NX_TraceFilterBuf));
        err = NX_EventSourceClearFilter(source, kbuf.filter);
        break;
    case NX_TRACE_RESET_FILTER:
        err = NX_EventSourceResetFilter(source);
        break;
    default:
        break;
    }

    return err;
}

NX_PRIVATE NX_DriverOps EventSourceDriverOps = {
    .open = SourceDevOpen,
    .close = SourceDevClose,
    .read = SourceDevRead,
    .write = SourceDevWrite,
    .control = SourceDevControl,
};

NX_PRIVATE void EventSourceDriverInit(void)
{
    NX_Driver *driver = NX_DriverCreate(SOURCE_DRV_NAME, NX_DEVICE_TYPE_VIRT, 0, &EventSourceDriverOps);
    if (driver == NX_NULL)
    {
        NX_LOG_E("create driver failed!");
        return;
    }

    if (NX_DriverRegister(driver) != NX_EOK)
    {
        NX_LOG_E("register driver %s failed!", SOURCE_DRV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
    
    EventSourceDriver = driver;

    NX_LOG_I("init %s driver success!", SOURCE_DRV_NAME);
}

NX_PRIVATE void EventSourceDriverExit(void)
{
    NX_DriverCleanup(SOURCE_DRV_NAME);
}

#if defined(CONFIG_TRACE_EVENT)

#include <trace/sched.h>

NX_Error NX_TraceInit(void)
{
    TraceEventDriverInit();
    EventSourceDriverInit();

#if defined(CONFIG_TRACE_EVENT_SCHED)
    /* init sched trace */
    NX_InitSchedTraceEvent();
#endif

    return NX_EOK;
}

NX_Error NX_TraceExit(void)
{

#if defined(CONFIG_TRACE_EVENT_SCHED)
    /* exit sched trace */
    NX_ExitSchedTraceEvent();
#endif

    EventSourceDriverExit();
    TraceEventDriverExit();

    return NX_EOK;
}

#endif
